package jinyilw.swing.rlaf.ui;

import jinyilw.swing.visuals.comp.ArrowComp;

import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFormattedTextField;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerDateModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.FontUIResource;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicSpinnerUI;
import javax.swing.text.InternationalFormatter;
import java.awt.AWTEvent;
import java.awt.Component;
import java.awt.Container;
import java.awt.FocusTraversalPolicy;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.text.AttributedCharacterIterator;
import java.text.CharacterIterator;
import java.text.DateFormat;
import java.text.Format;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Map;

/**
 * @author Jinyi
 */
public class RSpinnerUI extends BasicSpinnerUI
{
	private static final ArrowButtonHandler nextButtonHandler = new ArrowButtonHandler(
			"increment", true);
	private static final ArrowButtonHandler previousButtonHandler = new ArrowButtonHandler(
			"decrement", false);
	public static ComponentUI createUI(JComponent c)
	{
		return new RSpinnerUI();
	}

	@Override
	public void installUI(JComponent c)
	{
		super.installUI(c);
		c.setOpaque(false);
	}

	@Override
	protected void installDefaults()
	{
		spinner.setLayout(createLayout());
		JComponent editor = spinner.getEditor();
		Font font = spinner.getFont();
		if (font != null && editor instanceof JSpinner.DefaultEditor)
		{
			JTextField tf = ((JSpinner.DefaultEditor) editor).getTextField();
			if (tf != null && tf.getFont() instanceof UIResource)
			{
				tf.setFont(new FontUIResource(font));
			}
		}
	}

	@Override
	public void update(Graphics g, JComponent c)
	{
		// paint(g, c);
	}

	@Override
	protected Component createPreviousButton()
	{
		ArrowComp b = new ArrowComp(SwingConstants.SOUTH);
		b.setName("Spinner.previousButton");
		b.addMouseListener(previousButtonHandler);
		// installPreviousButtonListeners(b);
		return b;
	}

	@Override
	protected Component createNextButton()
	{
		ArrowComp b = new ArrowComp(SwingConstants.NORTH);
		b.setName("Spinner.nextButton");
		b.addMouseListener(nextButtonHandler);
		// installNextButtonListeners(b);
		return b;
	}

	static class ArrowButtonHandler
			implements
			ActionListener,
			FocusListener,
			MouseListener,
			UIResource
	{
		final javax.swing.Timer autoRepeatTimer;
		final boolean isNext;
		JSpinner spinner = null;
		JButton arrowButton = null;

		ArrowButtonHandler(String name, boolean isNext)
		{
			// super(name);
			this.isNext = isNext;
			autoRepeatTimer = new javax.swing.Timer(60, this);
			autoRepeatTimer.setInitialDelay(300);
		}

		private JSpinner eventToSpinner(AWTEvent e)
		{
			Object src = e.getSource();
			while ((src instanceof Component) && !(src instanceof JSpinner))
			{
				src = ((Component) src).getParent();
			}
			return (src instanceof JSpinner) ? (JSpinner) src : null;
		}

		public void actionPerformed(ActionEvent e)
		{
			act(e);
		}

		private void select(JSpinner spinner)
		{
			JComponent editor = spinner.getEditor();

			if (editor instanceof JSpinner.DateEditor)
			{
				JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor;
				JFormattedTextField ftf = dateEditor.getTextField();
				Format format = dateEditor.getFormat();
				Object value;

				if (format != null && (value = spinner.getValue()) != null)
				{
					SpinnerDateModel model = dateEditor.getModel();
					DateFormat.Field field = DateFormat.Field
							.ofCalendarField(model.getCalendarField());

					if (field != null)
					{
						try
						{
							AttributedCharacterIterator iterator = format
									.formatToCharacterIterator(value);
							if (!select(ftf, iterator, field)
									&& field == DateFormat.Field.HOUR0)
							{
								select(ftf, iterator, DateFormat.Field.HOUR1);
							}
						} catch (IllegalArgumentException ignored)
						{
						}
					}
				}
			}
		}

		private boolean select(JFormattedTextField ftf,
				AttributedCharacterIterator iterator, DateFormat.Field field)
		{
			int max = ftf.getDocument().getLength();

			iterator.first();
			do
			{
				Map<?, ?> attrs = iterator.getAttributes();

				if (attrs != null && attrs.containsKey(field))
				{
					int start = iterator.getRunStart(field);
					int end = iterator.getRunLimit(field);

					if (start != -1 && end != -1 && start <= max && end <= max)
					{
						ftf.select(start, end);
					}
					return true;
				}
			} while (iterator.next() != CharacterIterator.DONE);
			return false;
		}

		private int getCalendarField(JSpinner spinner)
		{
			JComponent editor = spinner.getEditor();

			if (editor instanceof JSpinner.DateEditor)
			{
				JSpinner.DateEditor dateEditor = (JSpinner.DateEditor) editor;
				JFormattedTextField ftf = dateEditor.getTextField();
				int start = ftf.getSelectionStart();
				JFormattedTextField.AbstractFormatter formatter = ftf
						.getFormatter();

				if (formatter instanceof InternationalFormatter)
				{
					Format.Field[] fields = ((InternationalFormatter) formatter)
							.getFields(start);

					for (Format.Field field : fields)
					{
						if (field instanceof DateFormat.Field)
						{
							int calendarField;

							if (field == DateFormat.Field.HOUR1)
							{
								calendarField = Calendar.HOUR;
							} else
							{
								calendarField = ((DateFormat.Field) field)
										.getCalendarField();
							}
							if (calendarField != -1)
							{
								return calendarField;
							}
						}
					}
				}
			}
			return -1;
		}

		public void mousePressed(MouseEvent e)
		{
			if (SwingUtilities.isLeftMouseButton(e)
					&& e.getComponent().isEnabled())
			{
				spinner = eventToSpinner(e);
				autoRepeatTimer.start();

				focusSpinnerIfNecessary();
			}
		}

		public void mouseReleased(MouseEvent e)
		{
			autoRepeatTimer.stop();
			arrowButton = null;
			spinner = null;
		}

		private void act(AWTEvent e)
		{
			JSpinner spinner = this.spinner;
			if (!(e.getSource() instanceof javax.swing.Timer))
			{
				spinner = eventToSpinner(e);
				if (e.getSource() instanceof JButton)
				{
					arrowButton = (JButton) e.getSource();
				}
			} else
			{
				if (arrowButton != null && !arrowButton.getModel().isPressed()
						&& autoRepeatTimer.isRunning())
				{
					autoRepeatTimer.stop();
					spinner = null;
					arrowButton = null;
				}
			}
			if (spinner != null)
			{
				try
				{
					int calendarField = getCalendarField(spinner);
					spinner.commitEdit();
					if (calendarField != -1)
					{
						((SpinnerDateModel) spinner.getModel())
								.setCalendarField(calendarField);
					}
					Object value = (isNext)
							? spinner.getNextValue()
							: spinner.getPreviousValue();
					if (value != null)
					{
						spinner.setValue(value);
						select(spinner);
					}
				} catch (IllegalArgumentException | ParseException iae)
				{
					UIManager.getLookAndFeel().provideErrorFeedback(spinner);
				}
			}
		}

		public void mouseClicked(MouseEvent e)
		{
			Component c = e.getComponent();
			if (c.isEnabled())
				act(e);
		}

		public void mouseEntered(MouseEvent e)
		{
			if (spinner != null && !autoRepeatTimer.isRunning()
					&& spinner == eventToSpinner(e))
			{
				autoRepeatTimer.start();
			}
		}

		public void mouseExited(MouseEvent e)
		{
			if (autoRepeatTimer.isRunning())
			{
				autoRepeatTimer.stop();
			}
		}

		private void focusSpinnerIfNecessary()
		{
			Component fo = KeyboardFocusManager.getCurrentKeyboardFocusManager()
					.getFocusOwner();
			if (spinner.isRequestFocusEnabled() && (fo == null
					|| !SwingUtilities.isDescendingFrom(fo, spinner)))
			{
				Container root = spinner;

				if (!root.isFocusCycleRoot())
				{
					root = root.getFocusCycleRootAncestor();
				}
				if (root != null)
				{
					FocusTraversalPolicy ftp = root.getFocusTraversalPolicy();
					Component child = ftp.getComponentAfter(root, spinner);

					if (child != null
							&& SwingUtilities.isDescendingFrom(child, spinner))
					{
						child.requestFocus();
					}
				}
			}
		}

		public void focusGained(FocusEvent e)
		{
		}

		public void focusLost(FocusEvent e)
		{
			if (spinner == eventToSpinner(e))
			{
				if (autoRepeatTimer.isRunning())
				{
					autoRepeatTimer.stop();
				}
				spinner = null;
				if (arrowButton != null)
				{
					ButtonModel model = arrowButton.getModel();
					model.setPressed(false);
					model.setArmed(false);
					arrowButton = null;
				}
			}
		}
	}
}
